package pl.wroc.uni.ii.evolution.engine.operators.spacespecific.binaryvector.boa.bayesnetwork;

/**
 * @author Jarek Fuks, Zbigniew Nazimek
 *
 */

import pl.wroc.uni.ii.evolution.engine.EvPopulation;
import pl.wroc.uni.ii.evolution.engine.individuals.EvBinaryVectorIndividual;
import pl.wroc.uni.ii.evolution.engine.operators.spacespecific.binaryvector.boa.utils.EvBinary;

public class EvBinaryBayesianNetwork implements EvIBayesianNetwork<EvBinaryVectorIndividual> {

  protected EvPopulation<EvBinaryVectorIndividual> population;
  protected EvBinaryBayesianNode[] nodes; 
  protected int k = 4;
  protected boolean generated_probabilities;
  
  /**
   * 
   * @param k max number of parents in each node
   */
  public EvBinaryBayesianNetwork(int k) {
    generated_probabilities = false;
    this.k = k;
  }
  
  /**
   * add edge
   * @param from index of parent of edge
   * @param to index of child of edge
   */
  public boolean addEdge(int from, int to) {
    if (!legalEdge(from, to)) return false;
    nodes[to].addParent(nodes[from]);
    generated_probabilities = false;
    return true;
  }

  /**
   * remove edge
   * @param from index of parent of edge
   * @param to index of child of edge
   */
  public boolean removeEdge(int from, int to) {
    if (!nodes[to].isParent(nodes[from])) return false; 
    nodes[to].removeParent(nodes[from]);
    generated_probabilities = false;
    return true ;
  }
  
  /**
   * generate new individual, depends of probabilities in each mode 
   */
  public EvBinaryVectorIndividual generate() {
    if (!generated_probabilities) {
      generatePropabilities();
      generated_probabilities = true;
    }
    
    int nodes_size = nodes.length;
    int i=0;
    for (i=0; i < nodes_size; i++) {
      nodes[i].initGeneration();
    }
    
    boolean[] tab = new boolean[nodes.length];
    int tab_length = tab.length;
    for (i = 0;i < tab_length;i++){
      tab[i] = nodes[i].generate();
    }
    EvBinaryVectorIndividual ind = new EvBinaryVectorIndividual(tab);
    ind.setObjectiveFunction(population.get(0).getObjectiveFunction());
    return ind;
  }

  private void generatePropabilities() {
    int[] parents_indexes;
    int[] all_indexes;
    EvBinaryBayesianNode node;
    for (int n = 0;n < nodes.length; n++) {
      node = nodes[n];
      EvBinaryBayesianNode[] parents = node.getParents();
      
      
      parents_indexes = new int[parents.length];
      all_indexes = new int[parents.length + 1];
      for (int j = 0; j < parents_indexes.length; j++) {
        parents_indexes[j] = parents[j].getIndex();
        all_indexes[j] = parents[j].getIndex();
      }
      all_indexes[all_indexes.length - 1] = node.getIndex();
      
      
      double[] probabilities = new double[EvBinary.pow2(parents.length)];
      
      for (int i = 0; i< probabilities.length; i++) {
        boolean[] parents_value = EvBinary.intToBools(i, parents.length);
        boolean[] parents_and_true = EvBinary.intToBools(i, parents.length+1);
        parents_and_true[parents_and_true.length - 1] = true;
        
        
        int count_parents = EvBinary.numberOf(population, parents_value, parents_indexes);
        int count_all = EvBinary.numberOf(population, parents_and_true, all_indexes);
        
        if(count_parents == 0) probabilities[i] = 0.5;
        else probabilities[i] = count_all/(double)count_parents;
        
      }
      node.setProbabilities(probabilities);
    }
    
  }

  /**
   * @param child_index index of child
   * @return indexes of parent of child
   */
  public int[] getParentsIndexes(int child_index) {
    EvBinaryBayesianNode[] parents = nodes[child_index].getParents();
    int[] parent_indexes = new int[parents.length];
    int i = 0;
    for (EvBinaryBayesianNode node:parents){
      parent_indexes[i++] = node.getIndex();
    }
    return parent_indexes;
  }

  /**
   * set new population, generates new nodes in network
   */
  public void initialize(EvPopulation<EvBinaryVectorIndividual> population) {
    this.population = population;
    nodes = new EvBinaryBayesianNode[population.get(0).getDimension()];
    for (int i = 0; i < nodes.length; i++) {
      nodes[i] = new EvBinaryBayesianNode(i);
    }
    generated_probabilities = false;
  }
  
  protected boolean legalEdge(int n0,int n1){
    return ((!nodes[n1].searchForDescendant(nodes[n0]))  &&
        (!nodes[n1].isParent(nodes[n0])) &&
        (n0 != n1) &&
        (nodes[n1].getParents().length < k));
  }

  /**
   * @return number of nodes in network
   */
  public int getSize() {
    return nodes.length;
  }

  public EvBinaryBayesianNetwork clone() {
    EvBinaryBayesianNetwork new_net = new EvBinaryBayesianNetwork(k);
    new_net.initialize(population);
    for (EvBinaryBayesianNode node : nodes) {
      for (EvBinaryBayesianNode parent:node.getParents()) {
        new_net.addEdge(node.getIndex(), parent.getIndex());
      }
    }
    return new_net;
  }
 
  
  @Override
  public String toString() {
    StringBuilder builder = new StringBuilder();
    builder.append("Current network\n");
    for (int i = 0; i < nodes.length; i++) {
      builder.append(nodes[i]);
    }
    
    
    return builder.toString();
  }
 

}
